package com.ruoyi.common.utils.min_io;

import com.ruoyi.common.config.MinIOConfig;
import com.ruoyi.common.utils.file.CustomMultipartFile;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import io.minio.*;
import io.minio.errors.*;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


@Slf4j
public class MinIOUtil {


    public static MinioClient getMinioClient() {
        return SpringUtils.getBean(MinioClient.class);
    }


    /**
     * putObject上传文件
     *
     * @param file    源文件
     * @param saveUrl 文件保存文件夹
     * @return filePath
     * @throws IOException               当读取文件流时发生错误
     * @throws ServerException           当MinIO服务器发生错误
     * @throws InsufficientDataException 当读取的数据不足
     * @throws InternalException         当发生内部错误
     * @throws InvalidResponseException  当响应无效
     * @throws InvalidKeyException       当密钥无效
     * @throws NoSuchAlgorithmException  当算法不可用
     * @throws XmlParserException        当解析XML时发生错误
     * @throws ErrorResponseException    当响应错误
     */
    public static Map<String, String> putObject(File file, String saveUrl) throws IOException, ServerException, InsufficientDataException, InternalException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, XmlParserException, ErrorResponseException {
        try {
            // 原始文件名
            String originalFilename = file.getName();
            // 文件名
            String fileName = FileUtils.getFileName(originalFilename);
            // 文件后缀
            String suffix = FileUtils.getFileExtension(originalFilename);
            // 文件流
            @Cleanup
            InputStream inputStream = Files.newInputStream(file.toPath());
            // 文件大小
            long size = file.length();
            // 文件路径
            String filePath = createFilePath(fileName + "_" + System.currentTimeMillis() + "." + suffix);
            // 存储方法 putObject
            getMinioClient().putObject(PutObjectArgs.builder()
                    .bucket(MinIOConfig.getMinioBucketName())
                    .object(saveUrl + filePath)
                    .stream(inputStream, size, -1)
                    .contentType(getContentType(originalFilename))
                    .build());
            // 创建结果集
            HashMap<String, String> result = new HashMap<>();
            // 存储文件路径
            result.put("filePath", filePath);
            // 存储文件URL
            result.put("fileUrl", MinIOConfig.getMinioEndpoint() + "/" + MinIOConfig.getMinioBucketName() + "/" + saveUrl + filePath);
            // 存储文件名
            result.put("fileName", getFileName(filePath));
            return result;
        } catch (Exception e) {
            // 返回结果集
            // 创建结果集
            HashMap<String, String> result = new HashMap<>();
            // 存储文件路径
            result.put("filePath", "");
            // 存储文件URL
            result.put("fileUrl", "");
            // 存储文件名
            result.put("fileName", "");
            log.error("e: ", e);
            return result;
        }
    }

    /**
     * 根据文件扩展名获取Content-Type
     */
    private static String getContentType(String fileName) {
        if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
            return "image/jpeg";
        } else if (fileName.endsWith(".png")) {
            return "image/png";
        } else if (fileName.endsWith(".gif")) {
            return "image/gif";
        } else if (fileName.endsWith(".pdf")) {
            return "application/pdf";
        } else if (fileName.endsWith(".txt")) {
            return "text/plain";
        } else if (fileName.endsWith(".doc")) {
            return "application/msword";
        } else if (fileName.endsWith(".docx")) {
            return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        } else {
            return "application/octet-stream";
        }
    }

    /**
     * putObject上传文件
     *
     * @param file    源文件
     * @param saveUrl 文件保存文件夹
     * @return filePath
     * @throws IOException               当读取文件流时发生错误
     * @throws ServerException           当MinIO服务器发生错误
     * @throws InsufficientDataException 当读取的数据不足
     * @throws InternalException         当发生内部错误
     * @throws InvalidResponseException  当响应无效
     * @throws InvalidKeyException       当密钥无效
     * @throws NoSuchAlgorithmException  当算法不可用
     * @throws XmlParserException        当解析XML时发生错误
     * @throws ErrorResponseException    当响应错误
     */
    public static Map<String, String> putObject(MultipartFile file, String saveUrl) throws IOException, ServerException, InsufficientDataException, InternalException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, XmlParserException, ErrorResponseException {
        try {
            // 原始文件名
            String originalFilename = file.getOriginalFilename();
            // 文件名
            String fileName = FileUtils.getFileName(originalFilename);
            // 文件后缀
            String suffix = FileUtils.getFileExtension(originalFilename);
            // 文件流
            @Cleanup
            InputStream inputStream = file.getInputStream();
            // 文件大小
            long size = file.getSize();
            // 文件路径
            String filePath = createFilePath(fileName + "_" + System.currentTimeMillis() + "." + suffix);
            // 存储方法 putObject
            getMinioClient().putObject(PutObjectArgs.builder()
                    .bucket(MinIOConfig.getMinioBucketName())
                    .object(saveUrl + filePath)
                    .stream(inputStream, size, -1)
                    .contentType(file.getContentType())
                    .build());
            // 创建结果集
            HashMap<String, String> result = new HashMap<>();
            // 存储文件路径
            result.put("filePath", filePath);
            // 存储文件URL
            result.put("fileUrl", MinIOConfig.getMinioEndpoint() + "/" + MinIOConfig.getMinioBucketName() + "/" + saveUrl + filePath);
            // 存储文件名
            result.put("fileName", getFileName(filePath));
            return result;
        } catch (Exception e) {
            // 返回结果集
            // 创建结果集
            HashMap<String, String> result = new HashMap<>();
            // 存储文件路径
            result.put("filePath", "");
            // 存储文件URL
            result.put("fileUrl", "");
            // 存储文件名
            result.put("fileName", "");
            log.error("e: ", e);
            return result;
        }
    }

    public static Map<String, String> putObject(String fileCatalogue, MultipartFile file, String fileName) throws IOException, ServerException, InsufficientDataException, InternalException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, XmlParserException, ErrorResponseException {
        // 文件流
        @Cleanup
        InputStream inputStream = file.getInputStream();
        // 文件大小
        long size = file.getSize();
        // 文件路径
        String filePath = createFilePath(fileCatalogue, fileName);
        // 存储方法 putObject
        getMinioClient().putObject(PutObjectArgs.builder()
                .bucket(MinIOConfig.getMinioBucketName())
                .object(filePath)
                .stream(inputStream, size, -1)
                .contentType(file.getContentType())
                .build());

        HashMap<String, String> result = new HashMap<>();
        result.put("filePath", filePath);
        result.put("fileUrl", MinIOConfig.getMinioEndpoint() + "/" + MinIOConfig.getMinioBucketName() + "/" + filePath);
        result.put("fileName", getFileName(filePath));

        return result;
    }


    /**
     * 上传xlsx格式的Excel
     *
     * @param inputStream 文件流
     * @param fileName    文件名
     * @return 结果
     */
    public static Map<String, String> putXlsxExcelByStream(String fileCatalogue, InputStream inputStream, String fileName) throws IOException, ServerException, InsufficientDataException, InternalException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, XmlParserException, ErrorResponseException {
        // 文件大小
        // byte[] bytes = IOUtils.toByteArray(inputStream);
        // int length = bytes.length;
        long size = inputStream.available();
        // 文件路径
        String filePath = createFilePath(fileCatalogue, fileName);
        // 存储方法 putObject
        getMinioClient().putObject(PutObjectArgs.builder()
                .bucket(MinIOConfig.getMinioBucketName())
                .object(filePath)
                .stream(inputStream, size, -1)
                .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
                .build());

        HashMap<String, String> result = new HashMap<>();
        result.put("filePath", filePath);
        result.put("fileUrl", MinIOConfig.getMinioEndpoint() + "/" + MinIOConfig.getMinioBucketName() + "/" + filePath);
        result.put("fileName", getFileName(filePath));
        return result;
    }

    /**
     * 下载文件
     *
     * @param filePath 文件路径
     */
    public static void getObject(HttpServletResponse httpServletResponse, String filePath) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
        String fileName = getFileName(filePath);
        InputStream inputStream = getMinioClient().getObject(GetObjectArgs.builder()
                .bucket(MinIOConfig.getMinioBucketName())
                .object(filePath)
                .build());
        downloadFile(httpServletResponse, inputStream, fileName);
    }

    /**
     * 获取文件路径
     *
     * @param originalFilename 原始文件名称
     * @return FilePath
     */
    public static String createFilePath(String originalFilename) {
        return new SimpleDateFormat("yyyy/MM/dd").format(new Date()) + "/" + originalFilename;
    }

    /**
     * 获取文件路径
     * fileCatalogue 文件目录
     *
     * @param originalFilename 原始文件名称
     * @return FilePath
     */
    public static String createFilePath(String fileCatalogue, String originalFilename) {
        return fileCatalogue + "/" + new SimpleDateFormat("yyyy/MM/dd").format(new Date()) + "/" + originalFilename;
    }

    /**
     * 根据文件路径获取文件名称
     *
     * @param filePath 文件路径
     * @return 文件名
     */
    public static String getFileName(String filePath) {
        String[] split = StringUtils.split(filePath, "/");
        return split[split.length - 1];
    }

    /**
     * 下载文件
     *
     * @param httpServletResponse httpServletResponse
     * @param inputStream         inputStream
     * @param fileName            文件名
     * @throws IOException IOException
     */
    public static void downloadFile(HttpServletResponse httpServletResponse, InputStream inputStream, String fileName) throws IOException {
        // 设置响应头信息，告诉前端浏览器下载文件
        httpServletResponse.setContentType("application/octet-stream;charset=UTF-8");
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
        // 获取输出流进行写入数据
        OutputStream outputStream = httpServletResponse.getOutputStream();
        // 将输入流复制到输出流
        byte[] buffer = new byte[4096];
        int bytesRead = -1;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        // 关闭流资源
        inputStream.close();
        outputStream.close();
    }


    /**
     * 通过路径获取文件流
     *
     * @param filePath 文件路径
     * @return inputStream
     */
    public static InputStream getFileInputStream(String filePath) {
        InputStream stream = null;
        try {
            StatObjectResponse statObjectResponse =
                    getMinioClient().statObject(StatObjectArgs.builder().bucket(MinIOConfig.getMinioBucketName()).object(filePath).build());
            //以流的形式获取一个文件对象
            if (statObjectResponse != null && statObjectResponse.size() > 0) {
                stream = getMinioClient().getObject(
                        GetObjectArgs.builder()
                                .bucket(MinIOConfig.getMinioBucketName())
                                .object(filePath)
                                .build());
            }
        } catch (Exception e) {
            log.error("minio 文件获取报错", e);
        }
        return stream;
    }

    /**
     * 通过url获取文件流
     *
     * @param filePath 文件路径
     * @return inputStream
     */
    public static InputStream getFileInputStreamByUrl(String filePath) {
        InputStream stream = null;
        try {
            URL url = new URL(MinIOConfig.getMinioEndpoint() + "/" + MinIOConfig.getMinioBucketName() + "/" + filePath);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            stream = connection.getInputStream();
        } catch (Exception e) {
            log.error("通过url获取文件流异常:{}", e.getMessage(), e);
        }
        return stream;
    }

    /**
     * 从MinIO下载文件并转换为MultipartFile
     */
    public static MultipartFile getFileMultipartFile(String filePath) {
        InputStream stream = null;
        try {
            URL url = new URL(filePath);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            stream = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[1024];

            while ((nRead = stream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }

            byte[] fileContent = buffer.toByteArray();

            // 获取对象的元数据以获取内容类型
            String contentType = "application/octet-stream"; // 默认类型

            return new CustomMultipartFile(
                    fileContent,
                    getFileName(filePath),
                    getFileName(filePath),
                    contentType
            );
        } catch (Exception e) {
            log.error("通过url获取文件流异常:{}", e.getMessage(), e);
            return null;
        }

    }

    /**
     * 删除文件
     *
     * @param filePath 文件路径
     */
    public static void removeFile(String filePath) {
        try {
            // 判断桶是否存在
            boolean res = getMinioClient().bucketExists(BucketExistsArgs.builder().bucket(MinIOConfig.getMinioBucketName()).build());
            filePath = filePath.replace(MinIOConfig.getMinioEndpoint() + "/" + MinIOConfig.getMinioBucketName() + "/", "");
            if (res) {
                // 删除文件
                getMinioClient().removeObject(RemoveObjectArgs.builder()
                        .bucket(MinIOConfig.getMinioBucketName())
                        .object(filePath)
                        .build());
            }
        } catch (Exception e) {
            log.error("minio 删除文件失败", e);
        }
    }


    /**
     * 通过InputStream上传文件
     *
     * @param inputStream 文件输入流
     * @param bucketName  桶名称
     * @param objectName  对象名称
     * @param contentType 文件类型
     * @throws IOException               当读取文件流时发生错误
     * @throws ServerException           当MinIO服务器发生错误
     * @throws InsufficientDataException 当读取的数据不足
     * @throws InternalException         当发生内部错误
     * @throws InvalidResponseException  当响应无效
     * @throws InvalidKeyException       当密钥无效
     * @throws NoSuchAlgorithmException  当算法不可用
     * @throws XmlParserException        当解析XML时发生错误
     * @throws ErrorResponseException    当响应错误
     */
    public static void putObjectByStream(InputStream inputStream, String bucketName, String objectName, String contentType) throws IOException, ServerException, InsufficientDataException, InternalException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, XmlParserException, ErrorResponseException {
        // 获取输入流大小
        long size = inputStream.available();
        // 存储方法 putObject
        getMinioClient().putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .stream(inputStream, size, -1)
                .contentType(contentType)
                .build());
    }


}
